import decimal
import math
import uuid
#from SignalDispatcher import SignalDispatcher
import SignalDispatcher
from Serialization import Serializer
from RepeatTimer import RepeatTimer
    
def setID(id):
    if id is None:
        id = str(uuid.uuid4()).replace('-', '_')
        #id = id.urn
    return id

save_keys = {}
for key in ['saved_attributes', 'saved_child_classes', 'saved_child_objects']:
    save_keys.update({key:'_%s' % (key)})
    
class BaseObject(SignalDispatcher.dispatcher, Serializer):
    #_saved_class_name = 'BaseObject'
    _saved_attributes = ['categories_id']
    def __init__(self, **kwargs):
        cls = self.__class__
        #bases_limit = getattr(cls, '_saved_bases_limit', self._saved_class_name)
        save_dict = {}
        for key in save_keys.iterkeys():
            save_dict.update({key:set()})
        while cls != BaseObject.__bases__[0]:# and getattr(cls, '_saved_class_name', '') != bases_limit:
            if not hasattr(self, 'saved_class_name'):
                if hasattr(cls, '_saved_class_name'):
                    self.saved_class_name = cls._saved_class_name
            for key, val in save_keys.iteritems():
                if hasattr(cls, val):
                    save_dict[key] |= set(getattr(cls, val))
            cls = cls.__bases__[0]
        for key, val in save_dict.iteritems():
            if not hasattr(self, key):
                setattr(self, key, val)
                
        if not hasattr(self, 'root_category'):
            self.root_category = kwargs.get('root_category')
        self.categories = {}
        self.categories_id = kwargs.get('categories_id', set())
        
        SignalDispatcher.dispatcher.__init__(self, **kwargs)
        Serializer.__init__(self, **kwargs)
        
        self.register_signal('category_update')
        
        for c_id in self.categories_id:
            category = self.root_category.find_category(id=c_id)
            if category:
                self.add_category(category)
            else:
                bob
        
    def add_category(self, category):
        id = category.id
        self.categories.update({id:category})
        self.categories_id.add(id)
        if self not in category.members:
            category.add_member(self)
        self.emit('category_update', obj=self, category=category, state=True)
            
    def remove_category(self, category):
        if hasattr(category, 'name'):
            id = category.id
        else:
            id = category
        if self in self.categories[id].members:
            self.categories[id].del_member(self)
        self.categories.pop(id, None)
        self.categories_id.discard(id)
        self.emit('category_update', obj=self, category=category, state=False)
        
    def unlink(self):
        for category in self.categories.copy().values():
            category.del_member(self)
            
    def add_child_object(self, kwargs):
        kwargs.setdefault('root_category', self.root_category)
        if self.osc_enabled:
            kwargs.setdefault('osc_parent_node', self.osc_parent_node)
            
class Color(BaseObject):
    color_keys = ['red', 'green', 'blue']
    def __init__(self, **kwargs):
        self._rgb = [0] * 3
        super(BaseObject, self).__init__(**kwargs)
        
    def set_value(self, *args, **kwargs):
        if len(args):
            if type(args[0]) == list or type(args[0]) == tuple:
                self._rgb = args[0]
            else:
                self._rgb = args
        else:
            self.rgb = kwargs
            
    @property
    def rgb(self):
        return dict(zip(self.color_keys, self._rgb))
    @rgb.setter
    def rgb(self, d):
        for key, val in d.iteritems():
            self._rgb[self.color_keys.index(key)] = val
        
    @property
    def red(self):
        return self._rgb[0]
    @red.setter
    def red(self, value):
        self._rgb[0] = value
        
    @property
    def green(self):
        return self._rgb[1]
    @green.setter
    def green(self, value):
        self._rgb[1] = value
        
    @property
    def blue(self):
        return self._rgb[2]
    @blue.setter
    def blue(self, value):
        self._rgb[2] = value

class GenericPoll(BaseObject):
    def __init__(self, **kwargs):
        super(GenericPoll, self).__init__(**kwargs)
        self.interval = kwargs.get('interval', 2.0)
        self.polling = False
        self.interval_call = kwargs.get('interval_call')
        self.timer = None
    def start_poll(self, **kwargs):
        self.stop_poll()
        self.timer = RepeatTimer(self.interval, self.on_interval)
        self.polling = True
        self.timer.start()
    def stop_poll(self, **kwargs):
        if self.polling:
            self.polling = False
            self.timer.cancel()
        self.timer = None
    def on_interval(self):
        if self.interval_call is not None:
            self.interval_call()
    
class Scaler(BaseObject):
    def __init__(self, **kwargs):
        super(Scaler, self).__init__(**kwargs)
        self.register_signal('value_update')
        self.scales = {}
        scales = kwargs.get('scales')
        for key, val in scales.iteritems():
            sc_kwargs = val.copy()
            sc_kwargs.update({'name':key})
            if False:#sc_kwargs.get('LOG', False) is True:
                sc = LogScale(**sc_kwargs)
            else:
                sc = FloatScale(**sc_kwargs)
            self.scales.update({key:sc})
            self.scales[key].connect('value_update', self.on_scale_value_update)
    def set_value(self, scale_name, value):
        self.scales[scale_name].set_value(value)
        f_value = self.scales[scale_name].scaled
        for key, val in self.scales.iteritems():
            if key != scale_name:
                val.set_float_value(f_value)
                #if val.scaled != f_value:
                #    bob
                
    def get_value(self, scale_name, decimal_places=None):
        if decimal_places is None:
            return self.scales[scale_name].value
        q = decimal.Decimal(10) ** -decimal_places
        d = decimal.Decimal(str(self.scales[scale_name].value))
        return d.quantize(q)
        #fstr = str(decimal_places).join(['%.', 'f'])
        #s = fstr % (self.scales[scale_name].value)
        #return float(s)
        
    #def get_scaled(self, scale_in, scale_out):
    #    val_in = self.scales[scale_in].scaled
    #    val_out = val_in * self.scales[scale_out].max
    #    return val_out
    
    def on_scale_value_update(self, **kwargs):
        obj = kwargs.get('obj')
        if obj.name in self.scales:
            self.emit('value_update', obj=obj, name=obj.name, value=obj.value)

class FloatScale(BaseObject):
    def __init__(self, **kwargs):
        super(FloatScale, self).__init__(**kwargs)
        self.register_signal('value_update')
        self.name = kwargs.get('name')
        self.max = kwargs.get('max')
        self.min = kwargs.get('min', 0.0)
        self.value = kwargs.get('value', 0.0)
        self.offset_max = float(self.max - self.min)
        self.scale_factor = 1.0 / float(self.offset_max)
        #self.scaled_offset = 1.0 / float(self.min)
        
        self.scaled = self.do_scale_to_float()
    def set_value(self, value):
        if value != self.value:
            self.value = float(value)
            self.scaled = self.do_scale_to_float()
            #self.emit('value_update', obj=self, value=self.value)
    def set_float_value(self, value):
        self.value = self.do_scale_from_float(value)
        self.scaled = self.do_scale_to_float()
        self.emit('value_update', obj=self, value=value)
    def do_scale_from_float(self, value):
        return (value * self.offset_max) + self.min
    def do_scale_to_float(self, value=None):
        if value is None:
            value = self.value
        return float(value - self.min) * self.scale_factor
        
class LogScale(FloatScale):
    def __init__(self, **kwargs):
        max = kwargs.get('max')
        min = kwargs.get('min')
        if min < 0:
            self.log_max = float(-min)#float(max - min)
            self.log_offset = -min
        elif min == 0:
            self.log_max = float(max)
            self.log_offset = 0.0
        else:
            self.log_max = float(max - min)
            self.log_offset = min
        #self.log_max_amplitude = 10 ** (self.log_max / 20.0)
        #self.log_inverted = False
        super(LogScale, self).__init__(**kwargs)
        
    def set_value(self, value):
        value = float(value)
        if value != self.value:
            self.value = value
            #l = self._set_inv_log_value(value)
            #self.scaled = self.do_scale_to_float(l)
            self.scaled = self._log_to_float(value)
            
    def set_float_value(self, value):
        #scaled = self.do_scale_from_float(value)
        #self.value = self._set_log_value(scaled)
        #self.scaled = self.do_scale_to_float(self.value)
        self.value = self._float_to_log(value)
        self.scaled = value
        self.emit('value_update', obj=self, value=self.value)
            
#    def _set_log_value(self, value):
#        offset = value + self.log_offset
#        if offset <= 0.0:
#            l = 0.0
#            print 'zero'
#        else:
#            #l = self.log_max * math.log(offset, self.log_max)
#            l = self.log_max * math.log10(offset/10.0)
#        return l - self.log_offset
#        
#    def _set_inv_log_value(self, value):
#        offset = float(value) + self.log_offset
#        #f = self.log_max ** (offset / self.log_max)
#        f = 10.0 * (10 ** (offset / self.log_max))
#        return f - self.log_offset
    
    def _log_to_float(self, value):
        offset = value - self.max
        return 60. ** (offset / 120.)
    def _float_to_log(self, value):
        if value <= 0.0:
            return self.min
        #l = 60 * math.log10(float(value))
        l = 120. * math.log(value, 60.)
        offset = l + self.max
        if offset < self.min:
            return self.min
        return offset

def parse_csv(filename):
    file = open(filename, 'r')
    d = {}
    for line_num, line in enumerate(file):
        if line_num == 0:
            keys = [col for col in line.strip().split(',')]
            for key in keys:
                d.update({key:[]})
        else:
            items = [float(item.strip('dB')) for item in line.strip().split(',')]
            for i, item in enumerate(items):
                d[keys[i]].append(item)
    file.close()
    return d
    

if __name__ == '__main__':
    import matplotlib.pyplot as pyplot
    d = {'i':{'name':'i', 'min':2, 'max':255}, 
         'l':{'name':'l', 'min':-130, 'max':0, 'LOG':True}}
    sc = Scaler(scales=d)
    x = []
    y = []
    for i in range(sc.scales['i'].min, sc.scales['i'].max+1):
        sc.set_value('i', i)
        x.append(sc.scales['l'].value)
        y.append(sc.scales['i'].value)
        print [sc.scales['i'].value, sc.scales['l'].value]#, sc.scales['l'].log_value]
    pyplot.plot(x, y)
    x = []
    y = []
    for i in range (sc.scales['l'].min, sc.scales['l'].max+1):
        sc.set_value('l', i)
        x.append(sc.scales['l'].value)
        y.append(sc.scales['i'].value)
        #print [sc.scales['i'].value, sc.scales['l'].value]#, sc.scales['l'].log_value]
    #pyplot.plot(x, y)
    realvals = parse_csv('bus-scale.csv')
    #pyplot.plot(realvals['Data'], realvals['Value'])
    pyplot.show()
